﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using MPI;
using System.Threading;
using System.Threading.Tasks;

namespace Dwarf.Managed
{
    /// <summary>
    /// Solve the Dense Linear Algebra problem with hybrid parallelization.
    /// </summary>
    /// <remarks> 
    /// For the demonstration purposes of the project, 
    /// we have chosen matrix-matrix multiplication.
    /// </remarks>
    class Solver
    {
        /// <summary>
        /// Number of root process.
        /// </summary>
        public const int ROOT_PROCESS = 0;

        /// <summary>
        /// Short dwarf's description.
        /// </summary>
        public const String DWARF_NAME = "Dense Linear Algebra, managed hybrid kernel";

        /// <summary>
        /// The size of square matrix.
        /// </summary>
        public int msize;

        /// <summary>
        /// Left matrix.
        /// </summary>
        public double[][] lgrid;

        /// <summary>
        /// Right matrix.
        /// </summary>
        public double[][] rgrid;

        /// <summary>
        /// Result matrix.
        /// </summary>
        public double[][] result;

        /// <summary>
        /// Empty constructor.
        /// </summary>
        public Solver()
        {
        }

        /// <summary>
        /// Transpose a square matrix
        /// <param name=matrix>
        /// The ragged square array to transpose.  Returns a new ragged transposed array
        /// </param>
        /// <param name=n>
        /// Dimension of the square array
        /// </param>
        /// <typeparam name=T>
        /// Element type of the array
        /// </typeparam>
        /// </summary>
        static private T[][] Transpose<T>(T[][] matrix, int n)
        {
            var transpose = new T[n][];
            for (var i = 0; i < n; i++)
            {
                transpose[i] = new T[n];
            }
            for (var i = 0; i < n; i++)
            {
                transpose[i][i] = matrix[i][i];
                for (var j = i + 1; j < n; j++)
                {
                    transpose[i][j] = matrix[j][i];
                    transpose[j][i] = matrix[i][j];
                }
            }
            return transpose;
        }

        /// <summary>
        /// MPI.NET and TPL based method for matrix-matrix multiplication.
        /// </summary>
        public void Solve()
        {
            //Number of sent rows.
            int rowNumSend = 0;
            //Number of recieved rows.
            int rowNumRecv = 0;

            //Status of receiving.
            CompletedStatus status;



            if (Communicator.world.Rank == Solver.ROOT_PROCESS)
            {
                //Root Process

                //Give each worker process a row to start with
                var batch = 1;
                for (var j = 0; j < batch; j++)
                {
                    for (var i = 1; i < Communicator.world.Size; i++)
                    {
                        Communicator.world.Send(rowNumSend, i, rowNumSend);
                        if (rowNumSend < msize)
                        {
                            Communicator.world.Send(lgrid[rowNumSend], i, rowNumSend);
                            rowNumSend++;
                        }
                    }
                }

                //Loop until all rows sent and all results received
                var row = new Double[msize];
                while (rowNumRecv < msize)
                {
                    //Receive row results from any process.
                    Communicator.world.Receive(Communicator.anySource, Communicator.anyTag, ref row, out status);
                    var t = result[status.Tag];
                    result[status.Tag] = row;
                    row = t;
                    rowNumRecv++;

                    //Send next row (if any) to process  that completed.
                    //Row number out of range is all done signal
                    Communicator.world.Send(rowNumSend, status.Source, rowNumSend);
                    if (rowNumSend < msize)
                    {
                        Communicator.world.Send(lgrid[rowNumSend], status.Source, rowNumSend);
                        rowNumSend++;
                    }
                }
            }
            else
            {
                //Worker Process

                // transpose the right hand array to improve memory accessing
                var rtransposed = Transpose<Double>(rgrid, msize);

                //Process rows until told to stop.
                var leftRow = new Double[msize];
                var row = new Double[msize];
                while (true)
                {
                    //get left row and index
                    //row index out of bounds means all done
                    Communicator.world.Receive(Solver.ROOT_PROCESS, Communicator.anyTag, out rowNumSend);
                    if (rowNumSend >= msize) break;
                    Communicator.world.Receive(Solver.ROOT_PROCESS, Communicator.anyTag, ref leftRow);

                    // TPL directive Multiply matrix by left row.
                    Parallel.For(0, msize, delegate(int i)
                    {
                        var cell = 0.0;
                        var rightColumn = rtransposed[i];
                        for (var j = 0; j < msize; j++)                                 // Loop for number of columns in left matrix and rows in right matrix.
                        {
                            cell += leftRow[j] * rightColumn[j];
                        }
                        row[i] = cell;
                    });

                    //Return resulted row to root process.
                    Communicator.world.Send(row, Solver.ROOT_PROCESS, rowNumSend);
                }
            }
        }
    }
}

